package com.atguigu.gmall.activity.listener;

import com.atguigu.gmall.activity.mapper.SeckillGoodsMapper;
import com.atguigu.gmall.activity.util.CacheHelper;
import com.atguigu.gmall.activity.util.DateUtil;
import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.model.activity.OrderRecode;
import com.atguigu.gmall.model.activity.SeckillGoods;
import com.atguigu.gmall.model.activity.UserRecode;
import com.atguigu.gmall.mq.constants.MqConst;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 秒杀接收消息 实现类
 * Second
 * Kill
 * receiver
 * SecKillReceiver 秒杀接收消息 实现类
 */
@Component
public class SeckillReceiver {


    @Autowired
    private SeckillGoodsMapper seckillGoodsMapper;
    @Autowired
    private RedisTemplate redisTemplate;

    //接收消息 将DB的数据保存Redis缓存中
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(value = MqConst.QUEUE_TASK_23, durable = "true"),
            exchange = @Exchange(value = MqConst.EXCHANGE_DIRECT_TASK, type = ExchangeTypes.DIRECT, durable = "true"),
            key = {MqConst.ROUTING_TASK_23}
    ))
    public void clearRedis(Message message, Channel channel) {

        //1:从Mysql中查询 已经结束了的秒杀商品
        QueryWrapper<SeckillGoods> queryWrapper = new QueryWrapper<>();
        //开始时间在今天  此条件是多余的
        //1）结束时间在今天的23点之前   清理掉
        queryWrapper.le("end_time",new Date());
        // OR  默认是And的关系
        //2) 此商品已经无货了
        queryWrapper.or().le("stock_count",0);
        List<SeckillGoods> seckillGoodsList = seckillGoodsMapper.selectList(queryWrapper);
        if(!CollectionUtils.isEmpty(seckillGoodsList)){
            for (SeckillGoods seckillGoods : seckillGoodsList) {
                //商品 Hash类型
                if(redisTemplate.opsForHash().hasKey(RedisConst.SECKILL_GOODS
                        ,seckillGoods.getSkuId().toString())){
                    redisTemplate.opsForHash().delete(RedisConst.SECKILL_GOODS
                            ,seckillGoods.getSkuId().toString());//SkuId 是多少
                    //商品库存 List类型
                    redisTemplate.delete(RedisConst.SECKILL_STOCK_PREFIX + seckillGoods.getSkuId());
                    //状态位  更新状态状
                    //redisTemplate.convertAndSend("seckilldelete",seckillGoods.getSkuId() + ":1");
                }

            }
            //更新DB中秒杀的状态为2 表示此秒杀商品活动已经结束了
            SeckillGoods seckillGoodsUp = new SeckillGoods();
            seckillGoodsUp.setStatus("2");
            seckillGoodsMapper.update(seckillGoodsUp,queryWrapper);
        }


        //不让用户买第二次  设置24小时  自动 清理 不用我们再清理
        //抢购资格  设置 45分钟  （模仿12306卖票）
        //下单 （设置 2小时之内不付钱） 自动清理  与此同 DB中订单 取消订单


    }
    //接收消息 将DB的数据保存Redis缓存中
    @RabbitListener(bindings = {@QueueBinding(
            value = @Queue(value = MqConst.QUEUE_TASK_1, durable = "true", autoDelete = "false"),
            exchange = @Exchange(value = MqConst.EXCHANGE_DIRECT_TASK),
            key = MqConst.ROUTING_TASK_1
    )})
    public void seckillMethod(Message message, Channel channel) {
        try {
            //1:查询当天的秒杀商品
            QueryWrapper<SeckillGoods> queryWrapper = new QueryWrapper<>();
            //1)当天的商品
            queryWrapper.eq("DATE_FORMAT(start_time,'%Y-%m-%d')",
                    DateUtil.formatDate(new Date()));
            //2:必须审核通过的商品
            queryWrapper.eq("status", "1");
            //3:剩余库存大于0
            queryWrapper.gt("stock_count", 0);

            //当天符合秒杀商品集合
            List<SeckillGoods> seckillGoodsList = seckillGoodsMapper.selectList(queryWrapper);
            if (CollectionUtils.isEmpty(seckillGoodsList)) {
                return;
            }
            for (SeckillGoods seckillGoods : seckillGoodsList) {
                //2:保存到缓存中  （如果缓存中已经存在了 、不要保存了）
                //参数1：k1  String类型            Map  k1 = new HashMap   k1.put(k2,v)
                //参数2：k2  String类型  V：Object（底层肯定转成Json格式字符类型）
                if (!redisTemplate.opsForHash().hasKey(RedisConst.SECKILL_GOODS,
                        seckillGoods.getSkuId().toString())) {
                    //不存在 保存缓存
                    redisTemplate.opsForHash().put(RedisConst.SECKILL_GOODS,
                            seckillGoods.getSkuId().toString(), seckillGoods);
                    //3:防止超卖 保存商品的库存   1:Apple 11   3件    1 1 1
                    String[] ids = buildSeckillIds(seckillGoods.getSkuId(), seckillGoods.getStockCount());
                    redisTemplate.opsForList().leftPushAll(
                            RedisConst.SECKILL_STOCK_PREFIX + seckillGoods.getSkuId(), ids);
                    //4:提前将当前商品是否有库存  状态位  0：无仓库 1：有库存 快速判定是否还有商品‘
                    redisTemplate.convertAndSend("seckillpush",seckillGoods.getSkuId() + ":1");

                }
            }
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), true);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }


    //接收开始秒杀的  当前是 第二步：处理秒杀请求
    @RabbitListener(bindings = {@QueueBinding(
            value = @Queue(value = MqConst.QUEUE_SECKILL_USER, durable = "true", autoDelete = "false"),
            exchange = @Exchange(value = MqConst.EXCHANGE_DIRECT_SECKILL_USER),
            key = MqConst.ROUTING_SECKILL_USER
    )})
    public void seckillHandler(UserRecode userRecode,Message message,Channel channel) throws Exception {
//        秒杀开始
//        1、校验当前商品的库存
        String state = (String) CacheHelper.get(userRecode.getSkuId().toString());
        if(!StringUtils.isEmpty(state) && "1".equals(state)){
    //        2、校验用户的 限制他的购买量 一个用户只能购买一个商品  setnx  上锁
            //        redis setnx  如果存在了 就不设置了返回值 0  如果不存在 就设置 返回值1
            Boolean ifAbsent = redisTemplate.opsForValue().setIfAbsent(
                    "user:" + userRecode.getUserId() + "skuId:" + userRecode.getSkuId(),
                    userRecode.getUserId(),24, TimeUnit.HOURS);// 用户ID：3   商品ID是：30  key : user:3:skuId:30  setnx
            if(ifAbsent){
                //此用户有购买资格 （此用户第一次来购买）  秒杀商品搞活只搞2小时
                //3、扣减商品库存
                String o = (String) redisTemplate.opsForList().rightPop(
                        RedisConst.SECKILL_STOCK_PREFIX + userRecode.getSkuId());
                if(StringUtils.isEmpty(o)){
                    //        失败：抢购失败
                    //失败:原因商品已售完
                    //采用 Redis的发布、订阅模式  群发更新商品的状态位 由原来的1-》0
                    redisTemplate.convertAndSend("seckillpush",userRecode.getSkuId() + ":0");
                }else{
                    // 成功：抢购成功
                    // 4、生成抢购资格 (有了此资格就为了生成订单 从而付款）
                    OrderRecode orderRecode = new OrderRecode();
                    //用户ID
                    orderRecode.setUserId(userRecode.getUserId());
                    //秒杀商品的SkuId
                    SeckillGoods seckillGoods = (SeckillGoods) redisTemplate.opsForHash().
                            get(RedisConst.SECKILL_GOODS, userRecode.getSkuId().toString());
                    orderRecode.setSeckillGoods(seckillGoods);
                    //由于限购 只买一件
                    orderRecode.setNum(1);
                    //将秒杀资格保存在缓存中  45分之内
                    redisTemplate.opsForValue().set(
                            RedisConst.SECKILL_ORDERS + userRecode.getUserId()
                            ,orderRecode,45,TimeUnit.MINUTES);
                    //更新库存、更新DB  后续操作 ............不是秒杀的核心代码
                }
            }
        }
        //手动应答
        channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);
    }





    //构建防止超卖的商品ID数组
    private String[] buildSeckillIds(Long skuId, Integer stockCount) {
        //数组长度 按钮剩余库存来定的
        String[] ids = new String[stockCount];
        for (Integer i = 0; i < stockCount; i++) {
            ids[i] = skuId.toString();
        }
        return ids;
    }
}
